#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/debugfs.h>
#include <linux/securec.h>
#include <linux/version.h>
#include <linux/timer.h>

#include "drv_log.h"
#include "../common/dev_common.h"

#define DRIVER_NAME "fan-speed"
#define FAN_LOG_PREFIX "fan"

#define FAN_SPEED_SAMPLE_NUM 60
#define FAN_SPEED_SAMPLE_CYCLE_MS 1000

#define fan_err(fmt...) drv_err(FAN_LOG_PREFIX, fmt)
#define fan_warn(fmt...) drv_warn(FAN_LOG_PREFIX, fmt)
#define fan_info(fmt...) drv_info(FAN_LOG_PREFIX, fmt)
#define fan_debug(fmt...) drv_debug(FAN_LOG_PREFIX, fmt)

#ifndef DRV_CHECK_RETV
#define DRV_CHECK_RETV(a, v) {                                                                  \
        if (!(a)) {                                                    \
            fan_err("[%s %d] Drv_check:%s\n", __func__, __LINE__, #a); \
            return (v);                                                \
        }                                                              \
    }
#endif

#ifdef STATIC_SKIP
#define STATIC
#else
#define STATIC static
#endif

#ifdef FAN_DEBUG_ENABLE
#define FAN_DEBUG(fmt, args...) fan_info(fmt, ##args)
#else
#define FAN_DEBUG(fmt, args...)
#endif

#define FAN_NO_NEED_INIT_FLAG 0x5a

unsigned int fan_gpio = 436;
unsigned int fan_drv_init_flag = 0;


/*lint -save -e514*/
module_param(fan_gpio, uint, S_IRUGO); /*lint -restore*/

struct fan_speed_ctrl {
    unsigned long long irq_current;
    unsigned long long irq_prev;
    unsigned long long sample_cnt;
    unsigned int speed;
    unsigned int gpio;
    unsigned int sample_table[FAN_SPEED_SAMPLE_NUM];
    struct timer_list timer;
    struct dentry *debugfs_root;
};

struct fan_speed_ctrl fan_speed = { 0 };

STATIC irqreturn_t fan_gpio_irq_handle(int irq, void *data)
{
    struct fan_speed_ctrl *fan_spd = (struct fan_speed_ctrl *)data;

    fan_spd->irq_current++;

    return IRQ_HANDLED;
}

STATIC int fan_gpio_irq_request(struct fan_speed_ctrl *fan_spd)
{
    int ret;

    if (!gpio_is_valid(fan_spd->gpio)) {
        fan_err("gpio not available\n");
        return -EINVAL;
    }

    ret = gpio_request(fan_spd->gpio, DRIVER_NAME);
    if (ret) {
        fan_err("gpio [%d] request failed\n", fan_spd->gpio);
        return ret;
    }

    ret = request_irq(gpio_to_irq(fan_spd->gpio), fan_gpio_irq_handle,
        IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND | IRQF_SHARED, DRIVER_NAME, fan_spd);
    if (ret) {
        gpio_free(fan_spd->gpio);
        fan_err("request gpio irq error\n");
        return ret;
    }

    return 0;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0)
STATIC void fan_timeout_timer(struct timer_list *t)
{
    struct fan_speed_ctrl *fan_spd = from_timer(fan_spd, t, timer);

#else
STATIC void fan_timeout_timer(unsigned long priv)
{
    struct fan_speed_ctrl *fan_spd = (struct fan_speed_ctrl *)(uintptr_t)priv;
#endif

    unsigned int i = 0;
    unsigned int index;
    unsigned int speed = 0;

    /* record speed per second */
    index = fan_spd->sample_cnt % FAN_SPEED_SAMPLE_NUM;
    fan_spd->sample_table[index] = (unsigned int)((fan_spd->irq_current - fan_spd->irq_prev) / 2);
    fan_spd->sample_cnt++;

    /* calculate the speed per minute */
    if (fan_spd->sample_cnt < FAN_SPEED_SAMPLE_NUM) {
        for (i = 0; i < fan_spd->sample_cnt; i++) {
            speed += fan_spd->sample_table[i];
        }
        for (i = fan_spd->sample_cnt; i < FAN_SPEED_SAMPLE_NUM; i++) {
            speed += fan_spd->sample_table[fan_spd->sample_cnt - 1];
        }
        FAN_DEBUG("running less than 1 minute, speed : %u\n", speed);
    } else {
        for (i = 0; i < FAN_SPEED_SAMPLE_NUM; i++) {
            speed += fan_spd->sample_table[i];
        }
    }

    /* update fan speed to user mode */
    fan_spd->speed = speed;

    FAN_DEBUG("speed : %u, irq_current : %llu, irq_prev : %llu, fan_spd->sample_cnt: %llu\n", fan_spd->speed,
        fan_spd->irq_current, fan_spd->irq_prev, fan_spd->sample_cnt);

    fan_spd->irq_prev = fan_spd->irq_current;
    mod_timer(&fan_spd->timer, jiffies + msecs_to_jiffies(FAN_SPEED_SAMPLE_CYCLE_MS));
}

STATIC int fan_add_debugfs(struct fan_speed_ctrl *fan_spd)
{
    struct dentry *root = NULL;
    //struct dentry *node = NULL;

    root = debugfs_create_dir("fan", NULL);
    if (IS_ERR_OR_NULL(root)) {
        fan_err("failed to create debugfs dir\n");
        return -ENOMEM;
    }

    fan_spd->debugfs_root = root;

    debugfs_create_u32("fan_speed", S_IRUSR, root, (unsigned int *)&fan_spd->speed);
#if 0
    if (node == NULL) {
        debugfs_remove_recursive(root);
        fan_err("failed to create debugfs u32\n");
        return -ENOMEM;
    }
#endif
    return 0;
}

STATIC void fan_remove_debugfs(struct fan_speed_ctrl *fan_spd)
{
    debugfs_remove_recursive(fan_spd->debugfs_root);
}

int __init fan_drv_module_init(void)
{
    int ret = 0;
    unsigned int type;
    unsigned int version;
    struct fan_speed_ctrl *fan_spd = &fan_speed;

    fan_drv_init_flag = 0;

    ret = devdrv_get_platform_type(&type, &version);
    if (ret) {
        fan_err("get platform type error\n");
        return ret;
    }

    if (type != DEVDRV_PLATFORM_TYPE_ASIC) {
        fan_drv_init_flag = FAN_NO_NEED_INIT_FLAG;
        fan_info("platform type is %u, not support fan!!!\n", type);
        return 0;
    }

    ret = memset_s((void *)fan_spd, sizeof(struct fan_speed_ctrl), 0, sizeof(struct fan_speed_ctrl));
    DRV_CHECK_RETV((0 == ret), ret);

    fan_spd->gpio = fan_gpio;

    ret = fan_add_debugfs(fan_spd);
    if (ret) {
        fan_err("failed to add fan debugfs\n");
        return ret;
    }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0)
    timer_setup(&fan_spd->timer, fan_timeout_timer, 0);
#else
    setup_timer(&fan_spd->timer, fan_timeout_timer, (unsigned long)(uintptr_t)fan_spd);
#endif
    mod_timer(&fan_spd->timer, jiffies + msecs_to_jiffies(FAN_SPEED_SAMPLE_CYCLE_MS));

    ret = fan_gpio_irq_request(fan_spd);
    if (ret) {
        fan_remove_debugfs(fan_spd);
        del_timer_sync(&fan_spd->timer);
        fan_err("failed to request fan irq\n");
        return ret;
    }

    FAN_DEBUG("fan_drv load ok.\n");

    return 0;
}

void __exit fan_drv_module_exit(void)
{
    struct fan_speed_ctrl *fan_spd = &fan_speed;

    if (fan_drv_init_flag != FAN_NO_NEED_INIT_FLAG) {
        fan_remove_debugfs(fan_spd);
        del_timer_sync(&fan_spd->timer);
        free_irq(gpio_to_irq(fan_spd->gpio), fan_spd);
        gpio_free(fan_spd->gpio);
    }

    FAN_DEBUG("fan_drv unload ok...\n");
}

module_init(fan_drv_module_init);

module_exit(fan_drv_module_exit);

MODULE_DESCRIPTION("fan driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Huawei Tech. Co., Ltd.");
